Day 21 我們用幾何合併把六個碎框縫成一條跑道,但留下一個沒解決的問題,合併框太胖。跑道實際寬度大概 60 px,我們縫出來的框寬 224.9 px,長寬比只有 2.52。今天要處理這件事,用的是 Day 20 就講過但一直沒做的混合資料集。
先把 Day 20 那張交叉驗證表拿出來看:
| 模型 | 驗證集 | mAP50 |
|---|---|---|
| baseline(整圖訓練) | 原始整圖 valid | 0.495 |
| baseline(整圖訓練) | 切片 valid | 0.000 |
| tiled(切片訓練) | 原始整圖 valid | 0.099 |
| tiled(切片訓練) | 切片 valid | 0.205 |
對角線高、反對角線低,兩個模型各自只在自己的領域裡work。整圖模型有全局視野但看不見半截跑道,切片模型看得見半截跑道但忘了全局視野。
那如果把兩份資料餵給同一個模型呢?直覺上它應該要同時看過「一整座機場」跟「一截柏油路」,兩種感受野都學到。
最直覺的做法是把兩個資料夾複製到同一個地方,但這樣會出事,因為比例不對:
整圖 train: 18 張
切片 train: 96 張
1 比 5.33,切片會把整圖淹掉。模型看到的絕大多數樣本都是切片,訓練出來會很接近純切片模型。
那要不要複製整圖 5 份湊成 1:1?這裡有個更細的問題,該用什麼當分母。我先把標註實例數也數出來:
Find-airport-1 train 檔案 18 實例 21 空標註 0
find-airport-tiled train 檔案 96 實例 93 空標註 11
張數比是 1:5.33,實例比卻是 1:4.43。差異來自切片資料集裡那 11 張純背景(Day 20 刻意保留的負樣本),它們有圖片但沒有任何標註。拿張數當分母會被背景切片灌水。
模型實際在學的是標註實例,所以我用實例數來算過取樣倍率:
_, whole_inst = count_instances(WHOLE_ROOT / "train" / "labels") # 21
_, tiled_inst = count_instances(TILED_ROOT / "train" / "labels") # 93
repeat = max(1, round(tiled_inst / max(whole_inst, 1))) # 4
跑出來的結果:
train 實例數: 整圖 21 / 切片 93 -> 整圖過取樣 x4
[train] 整圖 72 + 切片 96 = 168 張 / 177 實例
[valid] 整圖 2 + 切片 12 = 14 張 / 18 實例
[test ] 整圖 2 + 切片 12 = 14 張 / 11 實例
實例數 84 對 93,接近 1:1。
BALANCE_SPLIT = "train" # 只有訓練集需要重取樣
過取樣是為了改變模型看到的資料分布,那是訓練階段的事。驗證集的職責是量測,把同一張圖重複四次只會讓指標失真,而且跟其他模型的驗證集不一致就沒得比了。
要講清楚一件事,把同一張圖複製四份不是把資料變成四倍。模型看到的還是那 18 張原圖,只是每個 epoch 會看到它們四次。真正讓四份副本長得不一樣的是 augmentation(fliplr / flipud / degrees=90),每次餵進去的翻轉角度都不同。
所以這是重新加權,不是資料增量。它解決的是「整圖樣本被切片淹掉」的問題,解決不了「原圖只有 18 張」的問題。
超參數刻意一個字都沒改,跟 Day 20 的 train_tiled.py 完全一樣:
model.train(
data=str(project_root / "find-airport-mixed" / "data.yaml"),
epochs=50, imgsz=640, batch=16, device="0",
name="airport_obb_mixed",
fliplr=0.5, flipud=0.5,
degrees=90.0, # 跟 train_tiled 對齊,把資料集之外的變因鎖住
)
這是刻意的實驗設計。三個模型如果連 augmentation 都不一樣,最後跑出差異也說不清楚是資料的功勞還是超參數的功勞。只讓一個變因動。
50 epochs 在 RTX 5070 Ti 上跑了 58 秒。
把 Day 20 的四格表擴成六格:
| 模型 | 驗證集 | P | R | mAP50 | mAP50-95 |
|---|---|---|---|---|---|
| baseline | 整圖 valid | 0.853 | 0.500 | 0.495 | 0.346 |
| baseline | 切片 valid | 0.000 | 0.000 | 0.000 | 0.000 |
| tiled | 整圖 valid | 0.189 | 0.500 | 0.099 | 0.069 |
| tiled | 切片 valid | 0.541 | 0.250 | 0.205 | 0.131 |
| mixed | 整圖 valid | 0.677 | 0.500 | 0.495 | 0.198 |
| mixed | 切片 valid | 0.661 | 0.250 | 0.245 | 0.111 |
混合模型兩邊都站穩了。整圖 mAP50 從切片模型的 0.099 拉回 0.495,切片 mAP50 從 0.205 拉到 0.245,precision 更是兩邊都比對應的專家模型高或接近(切片上 0.661 > tiled 的 0.541)。
Day 20 那個 0.000 的斷崖,被填平了。
有三個地方會騙人,得先講清楚:
2 張圖、2 個實例,所以 recall 只可能是 0、0.5、1.0 三個值,三個模型都是 0.500 不是因為它們一樣強,是因為都只抓到兩個裡的一個。切片 valid 的 16 個實例也一樣,0.250 就是抓到 4 個。混合模型找得到,但框得沒 baseline 那麼貼。第三點特別重要,因為今天的目標就是「把胖框瘦回去」,而驗證集的指標說框得更不準了。所以還是得回到真實衛星圖上看。
同一張 GeoTIFF、同一套 CLAHE 前處理、同樣 0.25 門檻,全部都套上 Day 21 的幾何合併:
[tiled slice320] 原始 6 框 -> 合併後 4 框
members=[0, 2, 4] conf=0.559 角度=145.6 長=566.0 寬=224.9 長寬比=2.52
members=[1] conf=0.354 角度= 56.2 長= 67.5 寬= 59.0 長寬比=1.14
members=[3] conf=0.316 角度= 2.0 長=154.1 寬=100.0 長寬比=1.54
members=[5] conf=0.273 角度= 30.1 長=182.2 寬= 87.7 長寬比=2.08
[mixed slice320] 原始 2 框 -> 合併後 1 框
members=[0, 1] conf=0.507 角度=142.0 長=467.3 寬= 92.4 長寬比=5.05
[mixed slice640] 原始 3 框 -> 合併後 2 框
members=[0, 1] conf=0.577 角度=143.4 長=369.5 寬=102.3 長寬比=3.61
members=[2] conf=0.327 角度= 50.4 長= 53.8 寬= 37.1 長寬比=1.45

中間那張是今天的成果,一個框、一條跑道、零誤判。
三個數字最有感:
59%
這次沒東西可過濾
左邊那三個橘色框(Day 21 得靠共線性檢查踢掉的離群碎片)在中間這張完全消失了。這代表誤判不是靠後處理擋掉的,是模型一開始就沒認錯。
右邊那張是同一個混合模型改切 640。結果是長寬比 3.61(比 320 差),而且多了一個 0.33 的誤判小框。
有點反直覺,因為混合模型明明看過整圖,理論上 640 應該更適合它。我的解讀是切 640 的時候,這張 1349x923 的圖只切出寥寥幾片,重疊區變少,SAHI 能拿來互相佐證的證據也變少了。切 320 雖然把跑道切得更碎,但碎片多、重疊多,反而讓 Day 21 的主軸擬合有足夠的點去對齊。
切片尺寸要配合的是推論時的資料密度,不只是訓練時的視野。
寬度 92.4 px 對上目測 60 px 的跑道,還是胖了 50%。要再瘦下去大概不是資料混合能解決的,得回到標註本身,或者用 segmentation 的遮罩去修 OBB。
更根本的問題是原圖只有 18 張。今天做的所有事情(切片、混合、過取樣)都是在同一批 18 張圖上重新排列組合,這是資料工程不是資料增量。驗證集只有 2 張圖 2 個實例,任何指標都很吵,六格表裡真正有說服力的其實只有 baseline 那個 0.000,因為只有它大到不可能是雜訊。
今天把整圖跟切片合成一個資料集,關鍵不是複製檔案,是用標註實例數而不是圖片張數來決定過取樣倍率,避開了純背景切片灌水的陷阱。
交叉驗證從 2x2 擴成 3x2,混合模型兩個領域都站穩,Day 20 那個 0.000 的斷崖被填平。回到真實衛星圖,合併框寬度從 224.9 瘦到 92.4、長寬比從 2.52 拉到 5.05,而且誤判框直接歸零。
但也得誠實說,驗證集小到指標會量化,mAP50-95 甚至退步了,真正撐住結論的是衛星圖上那張圖,不是那張表。
明天要把 Day 18 的 CLAHE 前處理、今天的混合模型、Day 21 的幾何合併串成一條完整的推論管線,讓這三天的成果變成一個函式吃 GeoTIFF 就能用,那我們明天見。